package com.google.code.guava.reflect.predicate;

import static com.google.common.base.Preconditions.checkNotNull;

import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;

public enum Qualifier{

    /**
     * Qualifier that checks for the <code>abstract</code> keyword.
     * 
     * @see Modifier#isAbstract(int)
     * @see Modifier#ABSTRACT
     */

    ABSTRACT(Modifier.ABSTRACT){

        @Override
        Collection<QualifiedType> supportedTypes(){
            return EnumSet.of(QualifiedType.ClassType, QualifiedType.MethodType);
        }
    },
    /**
     * Qualifier that checks for package-private (default) visibility. The
     * keyword <code>default</code> is illegal, so this Qualifier actually tests
     * for the absence of either {@link #PUBLIC}, {@link #PROTECTED} or
     * {@link #PRIVATE}.
     * 
     * @see #PRIVATE
     * @see #PROTECTED
     * @see #PUBLIC
     */
    DEFAULT(){

        @Override
        public boolean isCompatibleWith(final Qualifier other){
            return !EnumSet.of(PUBLIC, PRIVATE, PROTECTED)
            .contains(other);
        }

        @Override
        public boolean isPresent(final int presentModifiers){
            return PUBLIC.isAbsent(presentModifiers)
            && PRIVATE.isAbsent(presentModifiers)
            && PROTECTED.isAbsent(presentModifiers);
        }

        @Override
        public String toString(){
            return "(default)";
        }

    },
    /**
     * Qualifier that checks for the <code>final</code> keyword.
     * 
     * @see Modifier#isFinal(int)
     * @see Modifier#FINAL
     */
    FINAL(Modifier.FINAL),
    /**
     * Qualifier that checks for the <code>interface</code> keyword.
     * 
     * @see Modifier#isInterface(int)
     * @see Modifier#INTERFACE
     */
    INTERFACE(Modifier.INTERFACE){

        @Override
        Collection<QualifiedType> supportedTypes(){
            return EnumSet.of(QualifiedType.ClassType);
        }
    },
    /**
     * Qualifier that checks for the <code>native</code> keyword.
     * 
     * @see Modifier#isNative(int)
     * @see Modifier#NATIVE
     */
    NATIVE(Modifier.NATIVE){

        @Override
        Collection<QualifiedType> supportedTypes(){
            return EnumSet.of(QualifiedType.MethodType);
        }
    },
    /**
     * Qualifier that checks for <code>private</code> visibility.
     * 
     * @see Modifier#isPrivate(int)
     * @see Modifier#PRIVATE
     */
    PRIVATE(Modifier.PRIVATE){

        @Override
        public boolean isCompatibleWith(final Qualifier other){
            return !EnumSet.of(PUBLIC, PROTECTED, DEFAULT)
            .contains(other);
        };
    },
    /**
     * Qualifier that checks for <code>protected</code> visibility.
     * 
     * @see Modifier#isProtected(int)
     * @see Modifier#PROTECTED
     */
    PROTECTED(Modifier.PROTECTED){

        @Override
        public boolean isCompatibleWith(final Qualifier other){
            return !EnumSet.of(PUBLIC, PRIVATE, DEFAULT)
            .contains(other);
        }
    },
    /**
     * Qualifier that checks for <code>public</code> visibility.
     * 
     * @see Modifier#isPublic(int)
     * @see Modifier#PUBLIC
     */
    PUBLIC(Modifier.PUBLIC){

        @Override
        public boolean isCompatibleWith(final Qualifier other){
            return !EnumSet.of(PRIVATE, PROTECTED, DEFAULT)
            .contains(other);
        }
    },
    /**
     * Qualifier that checks for the <code>static</code> keyword.
     * 
     * @see Modifier#isStatic(int)
     * @see Modifier#STATIC
     */
    STATIC(Modifier.STATIC){

        @Override
        public boolean isCompatibleWith(final Qualifier other){
            return !EnumSet.of(ABSTRACT)
            .contains(other);
        }
    },
    /**
     * Qualifier that checks for the <code>strictfp</code> keyword.
     * 
     * @see Modifier#isStrict(int)
     * @see Modifier#STRICT
     */
    STRICT_FP(Modifier.STRICT){

        @Override
        Collection<QualifiedType> supportedTypes(){
            return EnumSet.of(QualifiedType.ClassType, QualifiedType.MethodType);
        }

        @Override
        public String toString(){
            return "strictfp";
        }
    },
    /**
     * Qualifier that checks for the <code>synchronized</code> keyword.
     * 
     * @see Modifier#isSynchronized(int)
     * @see Modifier#SYNCHRONIZED
     */
    SYNCHRONIZED(Modifier.SYNCHRONIZED){

        @Override
        Collection<QualifiedType> supportedTypes(){
            return EnumSet.of(QualifiedType.MethodType);
        }
    },
    /**
     * Qualifier that checks for the <code>transient</code> keyword.
     * 
     * @see Modifier#isTransient(int)
     * @see Modifier#TRANSIENT
     */
    TRANSIENT(Modifier.TRANSIENT){

        @Override
        Collection<QualifiedType> supportedTypes(){
            return EnumSet.of(QualifiedType.FieldType);
        }
    },
    /**
     * Qualifier that checks for the <code>volatile</code> keyword.
     * 
     * @see Modifier#isVolatile(int)
     * @see Modifier#VOLATILE
     */
    VOLATILE(Modifier.VOLATILE){

        @Override
        Collection<QualifiedType> supportedTypes(){
            return EnumSet.of(QualifiedType.FieldType);
        }
    };

    /**
     * Concatenate a single qualifier with an array of Qualifiers.
     * 
     * @param singleQualifier
     * @param moreQualifiers
     * @return an array of qualifiers
     */
    static Qualifier[] concat(final Qualifier singleQualifier,
        final Qualifier ... moreQualifiers){
        checkNotNull(moreQualifiers);
        if(moreQualifiers.length == 0)
            return new Qualifier[]{ checkNotNull(singleQualifier) };
        else return EnumSet.of(checkNotNull(singleQualifier), moreQualifiers)
        .toArray(new Qualifier[1 + moreQualifiers.length]);
    }

    /**
     * Given a modifiers bit mask, return all Qualifiers that apply to this
     * mask.<br />
     * Modifiers masks are generated by:
     * <ul>
     * <li>{@link java.lang.Class#getModifiers() Class.getModifiers()}</li>
     * <li>{@link java.lang.reflect.Method#getModifiers() Method.getModifiers()}
     * </li>
     * <li>{@link java.lang.reflect.Field#getModifiers() Field.getModifiers()}</li>
     * <li>{@link java.lang.reflect.Constructor#getModifiers()
     * Constructor.getModifiers()}</li>
     * </ul>
     * 
     * @param mask
     *            a bitmask generated by one of the <code>getModifiers()</code>
     *            methods
     * @return all applying qualifiers
     * @see Modifier
     */
    public static Iterable<Qualifier> qualifiersFromMask(final int mask){
        return Iterables.filter(Arrays.asList(values()),
            new Predicate<Qualifier>(){

            public boolean apply(final Qualifier candidate){
                return candidate.isPresent(mask);
            }
        });
    }

    private Integer mask;

    private Qualifier(){
        mask = null;
    }

    private Qualifier(final int mask){
        this.mask = Integer.valueOf(mask);
    }

    /**
     * Returns true if this Qualifier does not apply to the specified bit mask.
     * 
     * @see #isPresent(int)
     */
    public final boolean isAbsent(final int presentModifiers){
        return !isPresent(presentModifiers);
    }

    /**
     * Returns true if this Qualifier is compatible with the specified other
     * Qualifier.
     */
    public boolean isCompatibleWith(final Qualifier other){
        return true;
    }

    /**
     * Returns true if this Qualifier does apply to the specified bit mask.
     * 
     * @see #isAbsent(int)
     */
    public boolean isPresent(final int presentModifiers){
        Preconditions.checkState(mask != null,
            "Qualifier %s does not have a mask and does not overwrite isPresent(int)",
            name());
        return (presentModifiers & mask.intValue()) != 0;
    }

    /**
     * Return all {@link QualifiedType types} to which this Qualifier can apply.
     */
    Collection<QualifiedType> supportedTypes(){
        return EnumSet.allOf(QualifiedType.class);
    }

    @Override
    public String toString(){
        return name().toLowerCase();
    }

}
